import 'dart:async';
import 'dart:convert';
import 'dart:ui' as ui;

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

import 'package:scrawl/third_part/bloc/bloc.dart';

import '../bloc/scrawl_draw_controller.dart';
import '../model/pen/pen_style.dart';
import '../model/scrawl_data.dart';

class EditScrawlBloc extends Bloc<EditScrawlEvent, EditScrawlState> {
  final BuildContext context;
  EditScrawlBloc(this.context) {
    this.add(_LoadScrawlData());
    drawController = ScrawlDrawController();

    // for test
    // pic.jpg
    // l.jpg
    // s.jpg
    imageProvider = AssetImage('assets/images/l.jpg');
  }

  static const int ERASER_INDEX = -2;

  ScrawlDrawController drawController;

  final imageWidgetKey = GlobalKey();
  final touchWidgetKey = GlobalKey();
  Rect get imageBounds {
    final RenderBox imageBox = imageWidgetKey.currentContext.findRenderObject();
    Rect imageRect = imageBox.paintBounds;
    return imageRect;
  }

  Rect get imageRectInTouchWidget {
    final RenderBox imageBox = imageWidgetKey.currentContext.findRenderObject();
    final RenderBox touchBox = touchWidgetKey.currentContext.findRenderObject();
    Offset imageOrigin = imageBox.localToGlobal(Offset.zero);
    imageOrigin = touchBox.globalToLocal(imageOrigin);
    Rect imageRect = imageBox.paintBounds;
    imageRect = imageRect.shift(imageOrigin);

    return imageRect;
  }

  ImageProvider imageProvider;

  /// 画笔列表
  List<ScrawlFile> scrawlList;
  int selectedIndex = -1;
  int selectedSize = 1;

  ScrawlFile currentScrawl;

  @override
  EditScrawlState get initialState => EditScrawlInitState();

  @override
  Future<void> close() {
    drawController.dispose();
    return super.close();
  }

  @override
  void onError(Object error, StackTrace stacktrace) {
    debugPrint(error.toString());
    debugPrint(stacktrace.toString());
    super.onError(error, stacktrace);
  }

  @override
  Stream<EditScrawlState> mapEventToState(EditScrawlEvent event) async* {
    if (event is CloseScrawl) {
      drawController.enableTouch = false;
      // BlocProvider.of<EditMenuBloc>(context).add(CloseSubmenu());
    }
    if (event is _LoadScrawlData) {
      // BlocProvider.of<EditMenuBloc>(context)
      //     .add(ShowSubmenuEvent(from: TabType.pen));
      drawController.enableTouch = true;
      yield* loadScrawl();
    }
    if (event is ChangeScrawl) {
      await selectScrawl(selectedSize, event.selectedIndex);
      yield ScrawlListUpdate();
    }
    if (event is ChangeScrawlSize) {
      await selectScrawl(event.selectedSize, selectedIndex);
      yield ScrawlListUpdate();
    }
    if (event is UpdateScrawlDrawing) {
      yield ScrawlListUpdate();
    }
    if (event is ClearScrawl) {
      drawController.clear();
    }
  }

  Stream<ScrawlListUpdate> loadScrawl() async* {
    if (scrawlList == null || scrawlList.length == 0) {
      String json =
          await rootBundle.loadString('assets/scrawl/scrawlFile.json');
      List list = jsonDecode(json);
      scrawlList = list.map((model) {
        return ScrawlFile.fromJson(model);
      }).toList();
    }
    await selectScrawl(selectedSize, selectedIndex);
    yield ScrawlListUpdate();
  }

  Future<void> selectScrawl(
    int size,
    int index,
  ) async {
    this.selectedSize = size;
    this.selectedIndex = index;

    switch (index) {
      case ERASER_INDEX:
        this.currentScrawl = null;
        drawController.useEraser = true;

        break;
      default:
        if (index >= 0 && index < this.scrawlList.length) {
          final scrawlFile = this.scrawlList[index];
          this.currentScrawl = scrawlFile;

          await _selectScrawl(size, scrawlFile);
        }

        break;
    }
  }

  Future<void> _selectScrawl(
    int size,
    ScrawlFile scrawlFile,
  ) async {
    ui.Image tapeImage;
    List<ui.Image> images;
    if (scrawlFile.lines.bgImageName != null &&
        scrawlFile.lines.bgImageName.isNotEmpty) {
      tapeImage = await load(
          context, AssetImage('assets/scrawl/${scrawlFile.lines.bgImageName}'));
    }
    if (scrawlFile.image.name != null && scrawlFile.image.name.length > 0) {
      images = [];
      for (String imageName in scrawlFile.image.name) {
        if (imageName == null || imageName.isEmpty) {
          continue;
        }
        images.add(await load(context, AssetImage('assets/scrawl/$imageName')));
      }
    }

    double scale = scrawlFile.scale[size];

    final config = PenStyle(
      lineColor: _getColorFromHex(scrawlFile.lines.color),
      lineWidth: scrawlFile.lines.width * scale,
      dashWidth: scrawlFile.lines.dashedWidth * scale,
      dashSpacing: scrawlFile.lines.dashedSpacing * scale,
      strokeLineColor: _getColorFromHex(scrawlFile.troke.color),
      strokeLineWidth: scrawlFile.troke.width * scale,
      shadowColor: _getColorFromHex(scrawlFile.lines.shadowColor),
      shadowRadius: scrawlFile.lines.shadowRadius * scale,
      tapeImage: tapeImage,
      scrawlImages: images,
      imageWidth: scrawlFile.image.width * scale,
      spacing: scrawlFile.image.spacing * scale,
      isVector: scrawlFile.image.isRotation,
    );

    drawController.penStyle = config;
  }

  static int _getColorFromHex(String hexColor) {
    if (hexColor == null || hexColor.isEmpty) {
      return 0;
    }
    hexColor = hexColor.toUpperCase().replaceAll("#", "");
    if (hexColor.length == 6) {
      hexColor = "FF" + hexColor;
    }
    return int.parse(hexColor, radix: 16);
  }

  static Future<ui.Image> load(
    BuildContext context,
    ImageProvider provider, {
    Size size,
  }) {
    final ImageConfiguration config =
        createLocalImageConfiguration(context, size: size);
    final Completer<ui.Image> completer = Completer<ui.Image>();
    final ImageStream stream = provider.resolve(config);
    ImageStreamListener listener;
    listener = ImageStreamListener(
      (image, bool sync) {
        stream.removeListener(listener);
        completer.complete(image.image);
      },
      onError: (dynamic exception, StackTrace stackTrace) {
        provider.obtainKey(config).then((key) {
          PaintingBinding.instance.imageCache.evict(key);
        });
        completer.completeError(exception, stackTrace);
        stream.removeListener(listener);
      },
    );
    stream.addListener(listener);
    return completer.future;
  }
}

/// region define event
abstract class EditScrawlEvent {}

// class HideScrawlList extends EditScrawlEvent {}
// class ShowScrawlList extends EditScrawlEvent {}
class CloseScrawl extends EditScrawlEvent {}

class _LoadScrawlData extends EditScrawlEvent {}

class ChangeScrawlSize extends EditScrawlEvent {
  final int selectedSize;

  ChangeScrawlSize(this.selectedSize);
}

class ChangeScrawl extends EditScrawlEvent {
  final int selectedIndex;

  ChangeScrawl(this.selectedIndex);
}

class ClearScrawl extends EditScrawlEvent {}

class UpdateScrawlDrawing extends EditScrawlEvent {}

/// endregion

/// region define state
abstract class EditScrawlState {}

class EditScrawlInitState extends EditScrawlState {}

class EditScrawlListLoaded extends EditScrawlState {}

// TODO: 细化回调时机 ↑↑
class ScrawlListUpdate extends EditScrawlState {}

/// endregion
